// StateManager.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Globalization;
using System.Reflection;
using System.Threading;

namespace Supremacy.Types
{
    namespace Supremacy.Types.Wintellect.Threading.StateManager
    {
        public sealed class StateManager<TStateEnum> where TStateEnum : State
        {
            private int m_currentState;
            private readonly StateTransition<TStateEnum>[] m_transitionMap;

            public StateManager(TStateEnum startState, StateTransition<TStateEnum>[] transitionMap)
            {
                m_currentState = startState.Value;
                m_transitionMap = transitionMap;
            }

            public TStateEnum GetCurrent()
            {
                return (TStateEnum) State.Fromint(typeof(TStateEnum), m_currentState);
            }

            public Boolean TryChange(TStateEnum desiredState)
            {
                int crntState = m_currentState;
                int desiredStateInt = desiredState.Value;
                foreach (StateTransition<TStateEnum> st in m_transitionMap)
                {
                    if (st.From != crntState)
                        continue;
                    if (st.To != desiredStateInt)
                        continue;
                    if (Interlocked.CompareExchange(ref m_currentState, st.To, crntState) == crntState)
                        return true;
                    if (st.Disposition == StateChangeDisposition.Optional)
                        return false;
                    throw new InvalidOperationException("Mandatory state transition couldn't occur.");
                }
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.InvariantCulture,
                        "Illegal state transition from {0} to {1}.",
                        State.Fromint(typeof(TStateEnum), crntState),
                        desiredState));
            }
        }
    }

    public enum StateChangeDisposition
    {
        MustHappen,
        Optional
    }

    public struct StateTransition<TStateEnum> : IEquatable<StateTransition<TStateEnum>> where TStateEnum : State
    {
        private readonly StateChangeDisposition m_disposition;
        private readonly int m_from, m_to;

        public StateTransition(TStateEnum from, TStateEnum to, StateChangeDisposition disposition)
        {
            m_from = from.Value;
            m_to = to.Value;
            m_disposition = disposition;
        }

        public int From
        {
            get { return m_from; }
        }

        public int To
        {
            get { return m_to; }
        }

        public StateChangeDisposition Disposition
        {
            get { return m_disposition; }
        }

        #region IEquatable<StateTransition<TStateEnum>> Members
        public Boolean Equals(StateTransition<TStateEnum> other)
        {
            return (m_from == other.m_from) && (m_to == other.m_to) && (m_disposition == other.m_disposition);
        }
        #endregion

        public override Boolean Equals(object obj)
        {
            if (obj is StateTransition<TStateEnum>) return Equals((StateTransition<TStateEnum>) obj);
            return false;
        }

        public static Boolean operator ==(StateTransition<TStateEnum> st1, StateTransition<TStateEnum> st2)
        {
            return st1.Equals(st2);
        }

        public static Boolean operator !=(StateTransition<TStateEnum> st1, StateTransition<TStateEnum> st2)
        {
            return !st1.Equals(st2);
        }

        public override int GetHashCode()
        {
            return (m_from | (m_to << 16));
        }
    }

    public abstract class State
    {
        private readonly int m_value;

        protected State(int value)
        {
            m_value = value;
        }

        public string Name
        {
            get { return ToString(); }
        }

        public int Value
        {
            get { return m_value; }
        }

        public int Toint()
        {
            return this;
        }

        public static implicit operator int(State state)
        {
            return state.m_value;
        }

        public override string ToString()
        {
            foreach (FieldInfo fi in GetType().GetFields(
                BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static))
            {
                if (m_value == ((State) fi.GetValue(this)).m_value)
                    return fi.Name;
            }
            return String.Empty;
        }

        internal static State Fromint(Type type, int value)
        {
            foreach (FieldInfo fi in type.GetFields(
                BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static))
            {
                State state = (State) fi.GetValue(null);
                if (value == state)
                    return state;
            }
            throw new InvalidOperationException(
                String.Format(
                    CultureInfo.InvariantCulture,
                    "The '{0}' type doesn't define a static field with a numeric value of '{1}'.",
                    type.GetType(),
                    value));
        }
    }
}